/*
 *  $Id$
 *  Copyright [2010] [Panxiaobo] 
 *  Licensed under the Apache License, Version 2.0 (the "License"); 
 *  you may not use this file except in compliance with the License. 
 *  You may obtain a copy of the License at
 *  
 *     http://www.apache.org/licenses/LICENSE-2.0
 *     
 *  Unless required by applicable law or agreed to in writing, software 
 *  distributed under the License is distributed on an "AS IS" BASIS, 
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 *  See the License for the specific language governing permissions and 
 *  limitations under the License. 
 */
package pxb.openid.op;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Calendar;
import java.util.Date;
import java.util.Random;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import pxb.openid.Association;
import pxb.openid.AssociationImpl;
import pxb.openid.Constants;
import pxb.openid.OpenIdException;
import pxb.openid.util.Base64;

/**
 * 将Association以字符串的方式保存,直接转换成其id值,以base64编码 具体格式如下
 * 
 * <pre>
 *  3位的字符'pxb' + 8位的时间 + 1位的类型 + 2位的随机值 + 20/32位的mac密钥
 * </pre>
 * 
 * @author Panxiaobo
 * 
 */
public abstract class EncodedAssociationManager implements AssociationManager {

    private static final Logger log = LoggerFactory.getLogger(EncodedAssociationManager.class);
    /**
     * 超时时间,单位：秒
     */
    protected int expires = 3600;// 1 hour

    /**
     * 随机数生成
     */
    private final Random random = new java.security.SecureRandom();

    /*
     * (non-Javadoc)
     * 
     * @see pxb.openid.op.AssociationManager#build(byte[], java.lang.String)
     */
    @Override
    public Association build(byte[] mac, String type) {
        AssociationImpl asso = new AssociationImpl();
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        calendar.add(Calendar.SECOND, expires);
        asso.setExpireTime(calendar.getTime());
        asso.setMac(mac);
        asso.setType(type);
        {
            byte[] data;

            try {
                // 3'pxb' + 8time + 1type + 2random + Nmac
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                DataOutputStream out = new DataOutputStream(baos);

                // 3 'pxb'
                out.write(new byte[] { 'p', 'x', 'b' });
                // 8 time
                long time = asso.getExpireTime().getTime();
                out.writeLong(time);
                // 1 type
                if (type.equals(Constants.HMAC_SHA1)) {
                    out.write(1);
                } else {
                    out.write(2);
                }
                // 2 random
                out.write(random.nextInt());
                out.write(random.nextInt());

                // N mac
                out.write(mac);
                out.flush();
                out.close();
                data = baos.toByteArray();
            } catch (IOException e) {
                throw new OpenIdException(e);
            }
            byte[] encoded = doEncode(data);
            String id = new String(Base64.encodeBase64(encoded));
            asso.setId(id);
        }
        return asso;
    }

    /**
     * 解密
     * 
     * @param data
     * @return
     */
    protected byte[] doDecode(byte[] data) {
        return data;
    }

    /**
     * 加密
     * 
     * @param data
     * @return
     */
    protected byte[] doEncode(byte[] data) {
        return data;
    }

    /*
     * (non-Javadoc)
     * 
     * @see pxb.openid.op.AssociationManager#get(java.lang.String)
     */
    @Override
    public Association get(String id) {

        log.debug("get Association with id: {}", id);

        AssociationImpl asso = new AssociationImpl();
        asso.setId(id);

        byte[] data = doDecode(Base64.decodeBase64(id.getBytes()));

        try {
            DataInputStream input = new DataInputStream(new ByteArrayInputStream(data));

            // 3 'pxb'
            String pxb = new String(new byte[] { input.readByte(), input.readByte(), input.readByte() });
            if (!pxb.equals("pxb")) {
                throw new RuntimeException("not validate id");
            }
            // 8 time
            {
                long time = input.readLong();
                asso.setExpireTime(new Date(time));
            }
            byte[] key;
            // 1 type
            if (input.readByte() == 1) {
                asso.setType(Constants.HMAC_SHA1);
                key = new byte[20];
            } else {
                asso.setType(Constants.HMAC_SHA256);
                key = new byte[32];
            }

            // skip 2random
            input.skipBytes(2);
            // N mac
            input.readFully(key);
            asso.setMac(key);
        } catch (IOException e) {
            throw new OpenIdException(e);
        }
        return asso;
    }

    /**
     * 获取超时时间
     * 
     * @return the expires
     */
    public int getExpires() {
        return expires;
    }

    /**
     * 设置超时时间
     * 
     * @param expires
     *            the expires to set
     */
    public void setExpires(int expires) {
        this.expires = expires;
    }
}
